#include <filesystem.h>
#include <io.h>

// Filesystem //////////////////////////////////////////////////////////////////

struct file directory[NR_DIR];
struct open_file lsof[NR_OPEN_FILES];

void init_filesystem(void)
{
  int i;
  for (i = 0; i < NR_DIR; i++) {
    directory[i].initialized = 0;
  }
  for (i = 0; i < NR_OPEN_FILES; i++) {
    lsof[i].initialized = 0;
  }
  initZeOSFat();
  init_devices_files();
}


int init_open_file(struct task_struct *ts, int fd, int dir, int flags)
{  
  int i;
  int of = -1;
  for (i = 0; of < 0 && i < NR_OPEN_FILES; i++) {
    if (!lsof[i].initialized) {
      of = i;
    }
  }
  if (of < 0) {
    return -EMFILE;
  }
  
  lsof[of].file = &directory[dir];
  lsof[of].nr_references = 1;
  lsof[of].access_mode = flags;
  lsof[of].zf_rw_pointer = 0;
  lsof[of].initialized = 1;
  
  ts->channels[fd].open_file = &lsof[of];
  ts->channels[fd].initialized = 1;

  // Crida al open dependent
   return (*ts->channels[fd].open_file->file->ops.open)(fd);
}

void init_file(struct file *f, const char *name, int permissions, void (*init_ops)(struct file_operations*))
{
  f->permissions = permissions;
  f->zf_first_block = EOF;
  f->size = 0;
  init_ops(&f->ops);
  strcopy(f->name, name);
  f->initialized = 1;
}

int init_new_file(const char *name, int permissions)
{
  int i;
  int dir = -1;
  permissions = permissions & O_RDWR;

  // Trobar un directori buit
  for (i = 0; dir < 0 && i < NR_DIR; i++) {
    if (!directory[i].initialized) {
       dir = i;
    }
  }
  if (dir < 0) {
    return -ENOSPC;
  }

  init_file(&directory[dir], name, permissions, sys_init_file);
  return dir;
}

int sys_open_file(int fd)
{
  return 0;
}

//FIXME: estilo
int sys_read_file(int fd, char *buffer, int size)
{
  struct file *f;
  struct open_file *of;
  struct task_struct *ts;
  int block;
  int byte;
  int read;
  ts = current();
  of = ts->channels[fd].open_file;
  f = of->file;
  
  byte = of->zf_rw_pointer % BLOCK_SIZE;
  block = zf_get_file_block(f->zf_first_block, of->zf_rw_pointer / BLOCK_SIZE);

  read = 0;
  while (of->zf_rw_pointer < f->size && read < size) {
    copy_to_user(&zf_data[block][byte], buffer, sizeof(Byte));
    read += sizeof(Byte);
    byte++;
    if (byte == BLOCK_SIZE) {
      block = zf_next_file_block(block);
      byte = 0;
    }
    buffer += sizeof(Byte);
    of->zf_rw_pointer++;
  }
  return read;
}

int sys_write_file(int fd, char *buffer, int size)
{
  struct file *f;
  struct open_file *of;
  struct task_struct *ts;
  int zf_block_index;
  int file_block;
  int byte;
  int written;
  if (size == 0) return 0;
  ts = current();
  of = ts->channels[fd].open_file;
  f = of->file;
  
  written = 0;
  // Hi ha bytes per escriure
  while (written < size) {
    // Trobem el block i el byte relatius al fitxer
    file_block = of->zf_rw_pointer / BLOCK_SIZE;
    byte = of->zf_rw_pointer % BLOCK_SIZE;
    // Si el punter de lectura escritura està al final del fitxer, vol dir que el proper que s'escriurà és un byte nou
    if (of->zf_rw_pointer >= f->size) {
      f->size++;
      // Si es el primer byte del block, hem de reservar un block nou a la ZeOSFat
      if (byte == 0) {
        zf_block_index = zf_get_free_block();
        // Si no s'ha pogut reservar un block no queda espai
        if (zf_block_index < 0) {
          f->size--;
          return -ENOSPC;
        }
        // Si es el primer byte del fitxer l'hem de guardar al file
        if (f->zf_first_block == EOF) {
          f->zf_first_block = zf_block_index;
        }
        // Altrement, es un block nou que va darrera de la resta
        else {
          zf_file_append_block(f->zf_first_block, zf_block_index);
        }
      }
      // Si no és el primer byte del block, només hem de trobar el block del fitxer que ja estarà començat
      else {
        zf_block_index = zf_get_file_block(f->zf_first_block, file_block);
      }
    }
    // Si no es un byte nou només hem de trobar el block del fitxer on va
    else {
      // Si es el primer que llegim busquem el block (que pot no ser el primer)
      if (written == 0) {
        zf_block_index = zf_get_file_block(f->zf_first_block, file_block);
      }
      // Si no es el primer que llegim i es el byte 0, vol dir que comencem un nou bloc
      else if (byte == 0) {
        zf_block_index = zf_next_file_block(zf_block_index);
      }
    }
    // Per copiar cal fer servir el index del block a la ZeOSFat
    copy_from_user(buffer, &zf_data[zf_block_index][byte], sizeof(Byte));
    // Incrementar els contadors
    written += sizeof(Byte);
    buffer += sizeof(Byte);
    of->zf_rw_pointer++;
  }
  return written;
}

int sys_close_file(int fd)
{
  return 0;
}

int sys_unlink_file(struct file *f)
{
  int i;
  int open_dir;
  int used = 0;
  for (i = 0; !used && (i < NR_OPEN_FILES); i++) {
    used = lsof[i].initialized && (lsof[i].file == f);
    if (used) {
      open_dir = i;
    }
  }
  if(used) {
    return -EBUSY; 
  }
  else {
    f->initialized = 0;
    zf_add_free_blocks(f->zf_first_block);
    f->zf_first_block = EOF;
  }
  return 0;
}

void sys_init_file(struct file_operations* ops)
{
  ops->open = sys_open_file;
  ops->read = sys_read_file;
  ops->write = sys_write_file;
  ops->close = sys_close_file;
  ops->unlink = sys_unlink_file;
}

int filename_ok(const char *path)
{
  if (strlen(path) > FILE_NAME_SIZE) {
    return -1;
  }
  return 0;
}

int find_file(const char *path)
{
  int i;
  for (i = 0; i < NR_DIR; i++){
    if (strcmp(directory[i].name, path) && directory[i].initialized)
      return i;
  }
  return -1;
}

int file_permissions_ok(struct file *f, int mode)
{
  mode &= O_RDWR;
  return (mode & f->permissions) == mode;
}

// ZeOSFAT /////////////////////////////////////////////////////////////////////

Byte zf_data[MAX_BLOCKS][BLOCK_SIZE];
int zf_allocation_table[MAX_BLOCKS];
int zf_free_block;

int initZeOSFat(void) {
  int i;
  zf_free_block = 0;
  // Inicialment cada bloc apunta al seguent bloc, que esta lliure
  for (i = 0; i < MAX_BLOCKS - 1; i++) {
    zf_allocation_table[i] = i + 1;
  }
  // Marquem l'últim
  zf_allocation_table[MAX_BLOCKS - 1] = EOF;
  return 0;
}

int zf_get_free_block(void)
{
  int selected = zf_free_block;
  if (selected == EOF) {
    return -1;
  }
  zf_free_block = zf_allocation_table[selected];
  zf_allocation_table[selected] = EOF;
  return selected;
}

void zf_add_free_blocks(int index)
{
  int free_block = zf_free_block;
  if (index == EOF) return;
  zf_free_block = index;
  while (zf_allocation_table[index] != EOF) {
    index = zf_allocation_table[index];
  }
  zf_allocation_table[index] = free_block;
}

int zf_next_file_block(int index)
{
  return zf_allocation_table[index];
}

int zf_get_file_block(int first, int n)
{
  int index = first;
  while (n > 0) {
    index = zf_allocation_table[index];
    n--;
  }
  return index;
}

void zf_file_append_block(int file, int last)
{
  while (zf_allocation_table[file] != EOF) {
    file = zf_allocation_table[file];
  }
  zf_allocation_table[file] = last;
  zf_allocation_table[last] = EOF;
}

int zf_add_file_block(int last)
{
  zf_allocation_table[last] = zf_get_free_block();
  return zf_allocation_table[last];
}

// Device Files ////////////////////////////////////////////////////////////////

void init_devices_files(void)
{
  // Inicialitzar el Display i el Keyboard al directory
  init_file(&directory[0], "DISPLAY", O_WRONLY, sys_init_console);
  init_file(&directory[1], "KEYBOARD", O_RDONLY, sys_init_keyboard);
}
